﻿#region Copyright
/// <copyright>
/// Copyright (c) 2008 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Web;
using Microsoft.Win32;

using Osh.Dvb;
using Osh.Media;

namespace Osh.ViewRight
{
	public class ViewRightApplication : MediaPlayerBase, Osh.Teletext.ITeletextStreamSource
	{
		private static TraceSource _traceSource = new TraceSource("Osh.ViewRight");
		private static string ViewRightKeyName = @"SOFTWARE\Verimatrix\ViewRight PC Player";
		private static string InstallDirValueName = "InstallDir";
		private static Regex HtmlBodyContentRegex = new Regex("<html><body>(.*)</body></html>", RegexOptions.IgnoreCase);

		private class Commands
		{
			public static string GetClientId = "getclientid";
			public static string GetMacAddress = "getmacaddress";
			public static string GetPlayerState = "getplayerstate";
			public static string Open = "open";
			public static string Play = "play";
			public static string Pause = "pause";
			public static string Stop = "stop";
		}
		private class Parameters
		{
			public static string FileName = "filename";
			public static string DisplayName = "displayname";
		}

		private const int PlayerMonitoringIntervalMs = 1000; // ms

		private string _installDir;
		private Process _playerProcess = null;
		private Timeline _timeline;
		private IMediaContentProvider _contentProvider;
		private ChannelStreamProcessor _channelStreamProcessor;
		private ushort? _teletextStreamPid;

		public ViewRightApplication(string name) : base(name)
		{
			_installDir = ReadInstallDir();
			_timeline = new Timeline(this);
			CreateProviders();
			base.PlayerMonitoringInterval = PlayerMonitoringIntervalMs;
			_channelStreamProcessor = new ChannelStreamProcessor();
			_channelStreamProcessor.PmtLoaded += new EventHandler<EventArgs>(ChannelStreamProcessor_PmtLoaded);
		}

		private void ChannelStreamProcessor_PmtLoaded(object sender, EventArgs e)
		{
			ProgramMapTable table = _channelStreamProcessor.ProgramMapTable;
			if(table == null)
				return;

			foreach(ProgramElementDefinition peDefinition in table.ProgramElements)
			{
				foreach(IDescriptor descriptor in peDefinition.Descriptors)
				{
					TeletextDescriptor teletextDescriptor = descriptor as TeletextDescriptor;
					if(teletextDescriptor == null)
						continue;

					_teletextStreamPid = peDefinition.Pid;
					return;
				}
			}
		}

		#region MediaPlayerBase Overrides
		public override void PlayMedia(IMedia media)
		{
			if(media == null)
				throw new ArgumentNullException("media");

			Uri url = media.Attributes.GetValue<Uri>(MediaAttributeNames.SourceUrl);
			if(url == null)
				return;

			_teletextStreamPid = null;
			_channelStreamProcessor.TuneToChannel(media);

			Uri command = BuildCommandUrl(Commands.Open, Parameters.FileName, url, Parameters.DisplayName, media.Name);
			Log.Info(TraceSource, "Setting media: '{0}'.", command);
			SendCommand(command, true);
			this.CurrentMedia = media;
		}

		public override void Play()
		{
			SendCommand(BuildCommandUrl(Commands.Play), true);
			this.State = GetPlayerState();
		}

		public override void Pause()
		{
			SendCommand(BuildCommandUrl(Commands.Pause), true);
			this.State = GetPlayerState();
		}

		public override void Stop()
		{
			SendCommand(BuildCommandUrl(Commands.Stop), true);
			this.CurrentMedia = null;
		}

		protected override PlayerState GetPlayerState()
		{
			if(this.State == PlayerState.Closed || _playerProcess == null)
			{
				Process[] processes = System.Diagnostics.Process.GetProcessesByName("ViewRight");
				if(processes.Length == 0)
					return PlayerState.Closed;

				_playerProcess = processes[0];

				if(!Initialize())
					return PlayerState.Closed;
			}

			if(!IsProcessAlive(_playerProcess))
			{
				_playerProcess = null;
				return PlayerState.Closed;
			}

			ViewRightPlayerState state = GetViewRightPlayerState();
			switch(state)
			{
				case ViewRightPlayerState.Unknown: return PlayerState.Ready;
				case ViewRightPlayerState.Idle: return PlayerState.Ready;
				case ViewRightPlayerState.Paused: return PlayerState.Paused;
			}

			return PlayerState.Playing;
		}

		protected override void OnStateChange()
		{
			if(this.State == PlayerState.Ready || this.State == PlayerState.Closed)
			{
				_teletextStreamPid = null;
				_channelStreamProcessor.TuneToChannel(null);
			}
		}

		protected override TraceSource TraceSourceInternal
		{
			get { return ViewRightApplication.TraceSource; }
		}

		public override ITimeline Timeline
		{
			get { return _timeline; }
		}
		#endregion MediaPlayerBase Overrides

		public static TraceSource TraceSource
		{
			get { return _traceSource; }
		}

		private void CreateProviders()
		{
			foreach(ProviderElement providerElement in ViewRightSection.Current.Providers)
			{
				if(!providerElement.Enabled)
					continue;

				try
				{
					Type providerType = Type.GetType(providerElement.Type, false);
					if(providerType == null)
						throw new TypeLoadException(string.Format("Provider type '{0}' not found.", providerElement.Type));
					IMediaContentProvider provider =
						(IMediaContentProvider)Activator.CreateInstance(providerType, new object[] { providerElement.Name, this });

					_contentProvider = provider;
					break;
				}
				catch(Exception e)
				{
					Log.Exception(TraceSource, e, "Cannot load provider '{0}'.", providerElement.Name);
				}
			}
		}

		private bool Initialize()
		{
			try
			{
				Log.Info(TraceSource, "Connecting to ViewRight PC player...");
				
				bool saveConfig = false;
				string clientId = SendCommand(BuildCommandUrl(Commands.GetClientId), true);
				if(!string.IsNullOrEmpty(clientId))
				{
					if(ViewRightSection.Current.ClientId != clientId)
					{
						ViewRightSection.Current.ClientId = clientId;
						saveConfig = true;
					}
				}

				string macAddress = SendCommand(BuildCommandUrl(Commands.GetMacAddress), true);
				if(!string.IsNullOrEmpty(macAddress))
				{
					macAddress = macAddress.Replace('-', ':');
					if(ViewRightSection.Current.MacAddress != macAddress)
					{
						ViewRightSection.Current.MacAddress = macAddress;
						saveConfig = true;
					}
				}
				
				if(saveConfig)
					Osh.Configuration.Config.Current.Save();

				Log.Info(TraceSource, "Connected to ViewRight PC player. Client ID = '{0}'. MAC address = '{1}'.",
					this.ClientId, this.MacAddress);

				return true;
			}
			catch(Exception e)
			{
				Log.Exception(TraceSource, e);
			}
			return false;
		}

		private ViewRightPlayerState GetViewRightPlayerState()
		{
			string playerStateStr = null;
			try
			{
				playerStateStr = SendCommand(BuildCommandUrl(Commands.GetPlayerState), true);
			}
			catch {}

			if(string.IsNullOrEmpty(playerStateStr))
				return ViewRightPlayerState.Unknown;
			playerStateStr = playerStateStr.Trim().ToLowerInvariant();
			if(playerStateStr == "idle")
				return ViewRightPlayerState.Idle;
			if(playerStateStr == "paused")
				return ViewRightPlayerState.Paused;
			if(playerStateStr == "fastforwarding")
				return ViewRightPlayerState.FastForwarding;
			if(playerStateStr == "rewinding")
				return ViewRightPlayerState.Rewinding;
			if(playerStateStr == "playing")
				return ViewRightPlayerState.Playing;
			return ViewRightPlayerState.Unknown;
		}

		public string ClientId
		{
			get { return ViewRightSection.Current.ClientId; }
		}

		public string MacAddress
		{
			get { return ViewRightSection.Current.MacAddress; }
		}

		public string InstallDir
		{
			get { return _installDir; }
		}

		private string ReadInstallDir()
		{
			using(RegistryKey key = Registry.LocalMachine.OpenSubKey(ViewRightKeyName,
				RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.QueryValues))
			{
				string path = null;
				if(key != null)
					path = (string)key.GetValue(InstallDirValueName, null);

				if(string.IsNullOrEmpty(path))
					throw new PlayerNotFoundException(typeof(ViewRightApplication),
						string.Format(@"Cannot read registry value 'HKLM\{0}\{1}'. " +
							"Make sure Verimatrix ViewRight PC Player is properly installed.",
							ViewRightKeyName, InstallDirValueName));

				string appPath = Path.Combine(path, "ViewRight.exe");
				if(!File.Exists(appPath))
					throw new PlayerNotFoundException(typeof(ViewRightApplication),
						string.Format(@"Cannot find player at '{0}'. Make sure Verimatrix ViewRight PC Player is properly installed.", appPath));

				return path;
			}
		}

		/// <summary>
		/// Sends command to application and returns HTTP response.
		/// </summary>
		/// <param name="url">Command in URL format. To create command URL use BuildCommandUrl method.</param>
		/// <param name="onlyBodyContent">If true, the method returns only content of HTML body tag.</param>
		/// <returns></returns>
		public string SendCommand(Uri url, bool onlyBodyContent)
		{
			string html = SendCommand(url);
			if(string.IsNullOrEmpty(html))
				return html;

			Match match = HtmlBodyContentRegex.Match(html);
			if(match.Success)
				return match.Groups[1].Value;

			return html;
		}

		public string SendCommand(Uri url)
		{
			HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
			request.Timeout = 1000;
			request.ReadWriteTimeout = 1000;
			using(HttpWebResponse response = (HttpWebResponse)request.GetResponse())
			{
				using(StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding(response.CharacterSet)))
					return reader.ReadToEnd();
			}
		}

		private Uri BuildCommandUrl(string command, params object[] args)
		{
			if(string.IsNullOrEmpty(command))
				throw new ArgumentNullException("command");

			StringBuilder sb = new StringBuilder();
			sb.AppendFormat("http://{0}/{1}", ViewRightSection.Current.EndPoint, command);
			
			for(int i = 0; i < args.Length; i += 2)
			{
				sb.Append(i == 0 ? "?" : "&");

				// No encoding
				if(i + 1 >= args.Length)
					sb.Append(args[i]);
				else
				{
					sb.Append(args[i]);
					sb.Append("=");
					sb.Append(args[i + 1]);
				}
			}
			return new Uri(sb.ToString());
		}

		#region ITeletextStreamSource Members
		public PacketStream OpenTeletextStream()
		{
			ushort? teletextPid = _teletextStreamPid;
			if(teletextPid == null)
				throw new InvalidOperationException("Cannot open teletext stream. Channel's teletext PID is unknown.");

			return _channelStreamProcessor.OpenStream(teletextPid.Value, PacketStreamType.PES);
		}
		#endregion ITeletextStreamSource Members
	}
}
